﻿using System;
using System.Collections.Generic;
using System.Composition;
using System.Globalization;
using System.Linq.Expressions;
using System.Threading;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;


namespace GaleSong
{
    /// <summary>
    ///     Denotes the interface that support custom string format.
    /// </summary>
    public interface IStringFormater
    {
        /// <summary>
        ///     Formats the specified content.
        /// </summary>
        /// <param name="content">The content.</param>
        /// <returns></returns>
        string Format(string content);
    }

    /// <summary>
    ///     Denotes the interface to provide image and string resource.
    /// </summary>
    public interface IResource
    {
        CultureInfo CurrentCulture { set; }
        string GetString(string name);
        ImageSource GetImage(string name);
    }

    /// <summary>
    ///     Defines the interface to manage resources.
    /// </summary>
    public interface IResourceService
    {
        void ChangeLanguage(string language);
        string GetString(string name, params object[] foramStrings);
        void SetBinding(object source, string sourceProperty, string key, IStringFormater stringFormater = null);

        void SetBinding<TSource>(TSource source, Expression<Func<TSource, object>> sourceProperty,
            Expression<Func<string>> keyExpression, IStringFormater stringFormater = null);

        void SetBinding(FrameworkElement element, DependencyProperty property, string key, string stringFormater = null);
        void ClearBinding(FrameworkElement element, DependencyProperty property);
        void ClearBinding(object source, string sourceProperty);

        void SetGlobalResource(IResource resource);

        ImageSource GetImage(string name);
        event EventHandler LanguageChanged;
    }

    /// <summary>
    ///     Default implementation of <see cref="IResourceService" />, it uses MEF to construct the <see cref="IResource" />.
    /// </summary>
    public class ResourceService : IResourceService
    {
        private IResource globalResource;

        private readonly Dictionary<WeakEntry, WeakStringBinding> _bindings =
            new Dictionary<WeakEntry, WeakStringBinding>();

        [ImportMany]
        public IResource[] Resources { get; set; }

        public void ChangeLanguage(string language)
        {
            CultureInfo culture = CultureInfo.CreateSpecificCulture(language);

            // 1. The following line provides localization for the application's user interface. 
            Thread.CurrentThread.CurrentUICulture = culture;

            // 2. The following line provides localization for data formats. 
            Thread.CurrentThread.CurrentCulture = culture;

            // 3. Set this culture as the default culture for all threads in this application. 
            CultureInfo.DefaultThreadCurrentCulture = culture;
            CultureInfo.DefaultThreadCurrentUICulture = culture;

            if (globalResource != null)
            {
                globalResource.CurrentCulture = culture;
            }

            Resources.ForEach(item => item.CurrentCulture = culture);

            Messengers.ResourceMessenger.Send(new LanguageChangedMessage());

            if(LanguageChanged != null)
            {
                LanguageChanged(this, null);
            }
        }

        public string GetString(string name, params object[] foramStrings)
        {
            if(Resources == null || name == null)
            {
                return string.Empty;
            }

            string str = null;

            if (globalResource != null)
            {
                str = globalResource.GetString(name);
            }

            if (string.IsNullOrEmpty(str))
            {
                foreach (IResource resource in Resources)
                {
                    str = resource.GetString(name);
                    if (str != null)
                    {
                        break;
                    }
                }
            }

            if(str != null && foramStrings != null)
            {
                return string.Format(str, foramStrings);
            }
            return str;
        }

        public void SetBinding(object source, string sourceProperty, string key, IStringFormater stringFormater = null)
        {
            WeakStringBinding weakBinding;
            var entry = new WeakEntry(source.GetHashCode(), sourceProperty);
            if(_bindings.TryGetValue(entry, out weakBinding))
            {
                weakBinding.StringFormat = stringFormater;
                weakBinding.UpdateKey(key);
            }
            else
            {
                weakBinding = new WeakStringBinding(source, sourceProperty, key, stringFormater);
                _bindings.Add(entry, weakBinding);
            }
        }

        public void SetBinding<TSource>(TSource source, Expression<Func<TSource, object>> sourceProperty,
            Expression<Func<string>> keyExpression, IStringFormater stringFormater = null)
        {
            SetBinding(source, sourceProperty.GetName(), keyExpression.GetName(), stringFormater);
        }

        public void SetBinding(FrameworkElement element, DependencyProperty property, string key,
            string stringFormater = null)
        {
            var stringResource = new StringResourceExtension();
            stringResource.Key = key;
            stringResource.StringFormat = stringFormater;
            BindingOperations.SetBinding(element, property, stringResource.SetBinding());
        }

        public void ClearBinding(FrameworkElement element, DependencyProperty property)
        {
            BindingOperations.ClearBinding(element, property);
        }

        public void ClearBinding(object source, string sourceProperty)
        {
            var entry = new WeakEntry(source.GetHashCode(), sourceProperty);
            if(_bindings.ContainsKey(entry))
            {
                _bindings.Remove(entry);
            }
        }

        public ImageSource GetImage(string name)
        {
            if(Resources == null)
            {
                return null;
            }

            ImageSource image = null;

            if (globalResource != null)
            {
                image = globalResource.GetImage(name);
            }

            if (image == null)
            {
                foreach (IResource resource in Resources)
                {
                    image = resource.GetImage(name);
                    if (image != null)
                    {
                        break;
                    }
                }
            }

            return image;
        }

        public void SetGlobalResource(IResource resource)
        {
            globalResource = resource;
        }

        public event EventHandler LanguageChanged;

        private struct WeakEntry
        {
            private readonly string _sourceProperty;
            private readonly int _targetHashcode;

            public WeakEntry(int targetHashcode, string sourceProperty)
            {
                _targetHashcode = targetHashcode;
                _sourceProperty = sourceProperty;
            }

            public override int GetHashCode()
            {
                unchecked
                {
                    int hash = 17;
                    hash = hash * 23 + _targetHashcode;
                    hash = hash * 23 + (_sourceProperty ?? String.Empty).GetHashCode();
                    return hash;
                }
            }

            public override bool Equals(object obj)
            {
                var entry = (WeakEntry)obj;
                return ((_targetHashcode == entry._targetHashcode) &&
                        (_sourceProperty == entry._sourceProperty));
            }

            public static bool operator ==(WeakEntry obj1, WeakEntry obj2)
            {
                return obj1.Equals(obj2);
            }

            public static bool operator !=(WeakEntry obj1, WeakEntry obj2)
            {
                return !(obj1 == obj2);
            }
        }
    }

    /// <summary>
    ///     Denotes the message.
    /// </summary>
    public interface IMessage
    {
    }

    /// <summary>
    ///     Concrete <see cref="IMessage" /> published when language changed.
    /// </summary>
    public class LanguageChangedMessage : IMessage
    {
    }
}